

#include "cybertron/core/UtilFile.hpp"
#include "../PlatformHeader.hpp"
#include "cybertron/core/UtilPath.hpp"
#include "cybertron/core/Log.hpp"

#include <locale>
#include <sstream>
#include <fstream>
#include <map>
#ifdef CYBERTRON_WIN
#include <filesystem>
#elif CYBERTRON_LINUX
#include <sys/types.h>
#include <sys/stat.h>
#endif

CYBERTRON_BEGIN

CybertronCoreAPI bool UtilFile::exist(const std::string& path)
{
// TODO: How to decide if one file is a symbol link or not?
// If it is a symbol link, how to decide it links to a folder or a regular file?
#ifdef CYBERTRON_WIN

    std::wstring inputPathW;
    try
    {
        std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
        inputPathW = utf8_conv.from_bytes(path);
    }
    catch (const std::exception &)
    {
        inputPathW.clear();
    }
    if (inputPathW.size() == 0)
        return false;
    DWORD attr(::GetFileAttributesW(inputPathW.c_str()));
    if (attr == 0xFFFFFFFF)
    {
	  // process error
      return false;
    }

    /*if (attr & FILE_ATTRIBUTE_REPARSE_POINT)
    {
		return true; // symbol link
	}*/
    return (attr & FILE_ATTRIBUTE_DIRECTORY)
        ? false /* a directory*/
        : true /* a regular file*/ ;
#else
    struct stat path_stat;
    if (::stat(path.c_str(), &path_stat) != 0)
    {
		// Nothing found
    	return false;
    }
	if (S_ISREG(path_stat.st_mode) /*|| S_ISLNK(path_stat.st_mode)*/)
	 	return true;
	return false;
#endif
}

CybertronCoreAPI bool UtilFile::executableExist(const std::string path)
{
#ifdef CYBERTRON_WIN
	return UtilFile::exist(std::string(path).append(std::string(".exe")));
#else
	return UtilFile::exist(path);
#endif
}

CybertronCoreAPI std::string UtilFile::readUTF8StringFromFile(const std::string& inputPath)
{
	std::fstream file;
	if (!tryOpen(file, inputPath, std::ios::in))
	{
		//logError("Failed to open file: [%s]", inputPath.c_str());
		throw std::runtime_error(u8"FileError");
	}

	std::stringstream ss;
	ss << file.rdbuf();
	return ss.str();
}

CybertronCoreAPI void UtilFile::writeUtf8StringToFile(
	const std::string& path,
	const std::string& content)
{
	std::fstream fs;
	tryOpen(fs, path, std::ios::out | std::ios::trunc | std::ios::binary);
	fs.write(content.c_str(), content.size());
}

CybertronCoreAPI bool UtilFile::tryLoad(const std::string& path, std::vector<std::uint8_t>& buffer)
{
	buffer.clear();

	std::fstream file;
	if (!tryOpen(file, path, std::ios::in | std::ios::binary))
	{
		return false;
	}

	file.seekg(0, std::ios::end);
	auto length = file.tellg();
	if (length > 0)
	{
		file.seekg(0, std::ios::beg);
		buffer.resize(static_cast<std::size_t>(length));
		file.read(reinterpret_cast<char *>(buffer.data()), static_cast<std::size_t>(length));
	}

	return true;
}

CybertronCoreAPI std::vector<std::uint8_t> UtilFile::load(const std::string& path)
{
	std::vector<std::uint8_t> buffer;
	if (!tryLoad(path, buffer))
	{
		throw std::runtime_error("bad_file_read: " + path);
	}
	return buffer;
}

CybertronCoreAPI bool UtilFile::trySave(
	const std::string& path,
	const std::uint8_t* pData,
	size_t size)
{
	std::fstream file;
	if (!tryOpen(file, path, std::ios::out | std::ios::trunc | std::ios::binary))
	{
		return false;
	}

	file.write(reinterpret_cast<const char *>(pData), size);
	if (!file.good())
	{
		return false;
	}

	return true;
}

CybertronCoreAPI bool UtilFile::trySave(
	const std::string& path,
	const std::vector<std::uint8_t>& buffer)
{
	return UtilFile::trySave(
		path,
		buffer.data(),
		buffer.size());
}

CybertronCoreAPI void UtilFile::save(
	const std::string& path,
	const std::vector<std::uint8_t>& buffer)
{
	if (!trySave(path, buffer))
	{
		throw std::runtime_error("bad_file_write: " + path);
	}
}

CybertronCoreAPI bool UtilFile::tryOpen(
	std::fstream& stream,
	const std::string& inputPath,
	int mode)
{

#ifdef CYBERTRON_WIN
	std::string dir = cybertron::UtilPath::getDir(inputPath);
	// TODO check dir before create
	std::experimental::filesystem::create_directories(dir);
	std::wstring inputPathW;
	try
	{
		std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
		inputPathW = utf8_conv.from_bytes(inputPath);
	}
	catch (const std::exception &)
	{
		inputPathW.clear();
	}

	if (inputPathW.size() > 0)
	{
		stream.open(inputPathW, mode);
	}
	else
	{
		stream.open(inputPath, mode);
	}
#else
    std::string dir = cybertron::UtilPath::getDir(inputPath);
	std::string cmd("mkdir -p ");
	system(cmd.append(dir).c_str());
	stream.open(inputPath, (std::ios_base::openmode)mode);
#endif
	return stream.good();
}

CYBERTRON_END
